/**
 * Copyright 1998-2006, CHISEL Group, University of Victoria, Victoria, BC, Canada.
 * All rights reserved.
 */
package ca.uvic.cs.cogz.mapping.ui;

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

import edu.stanford.smi.protege.resource.Icons;

/**
 * Balloon GUI that displays details about a mapping.
 * 
 * @author Chris Callendar, Sean Falconer
 * @date 15-Nov-06
 */
public class BalloonPanel extends JPanel {
	private static final long serialVersionUID = 1L;

	/** This constant will make the balloon show up above and to the right of the location. */
	public static final int ABOVE_RIGHT = 0;
	/** This constant will make the balloon show up above and to the left of the location. */
	public static final int ABOVE_LEFT = 1;
	/** This constant will make the balloon show up below and to the right of the location. */
	public static final int BELOW_RIGHT = 2;
	/** This constant will make the balloon show up below and to the left of the location. */
	public static final int BELOW_LEFT = 3;

	private static final int PAD = 8;
	private static final int SPIKE = 12;
	private static final int ICON_TEXT_GAP = 3;

	private JLabel titleLabel;
	private JLabel textLabel;
	private final Font defaultFont;
	private Color borderColor;
	private Color fillColor;
	private int balloonPosition;
	private boolean thickBorder;
	private Stroke borderStroke;

	public BalloonPanel() {
		this("");
	}

	public BalloonPanel(String text) {
		this(text, ABOVE_RIGHT);
	}

	public BalloonPanel(String text, int balloonPosition) {
		super(new BorderLayout(0, 0));
		this.balloonPosition = balloonPosition;
		this.thickBorder = false;
		this.borderStroke = new BasicStroke(1f);
		this.borderColor = Color.black;
		this.fillColor = Color.white;
		initialize();
		this.textLabel.setText(text);
		this.defaultFont = textLabel.getFont();
		adjustSize();
	}

	public BalloonPanel(String title, Icon titleIcon, String text) {
		this(title, titleIcon, text, ABOVE_RIGHT);
	}

	public BalloonPanel(String title, Icon titleIcon, String text, int balloonPosition) {
		this(text, balloonPosition);
		this.titleLabel.setText(title);
		this.titleLabel.setIcon(titleIcon);
		adjustTitleBorder();
		adjustSize();
	}

	private void initialize() {
		titleLabel = new JLabel();
		titleLabel.setOpaque(false);

		textLabel = new JLabel();
		//textLabel.setContentType("text/html");
		//textLabel.setWrapStyleWord(true);
		//textLabel.setLineWrap(true);
		//textLabel.setColumns(10);
		//textLabel.setEditable(false);
		textLabel.setOpaque(false);

		add(titleLabel, BorderLayout.NORTH);
		add(textLabel, BorderLayout.CENTER);

		setBorder(new BalloonBorder(getBorderInsets(balloonPosition)));
		setOpaque(false);
	}

	private void adjustSize() {
		int w = 0;
		int h = 0;
		if (titleLabel.getIcon() != null) {
			w += titleLabel.getIcon().getIconWidth() + titleLabel.getIconTextGap();
			h = titleLabel.getIcon().getIconHeight();
		}
		String title = titleLabel.getText();
		if ((title != null) && (title.length() > 0)) {
			FontMetrics metrics = titleLabel.getFontMetrics(titleLabel.getFont());
			w += metrics.stringWidth(title + "__");
			h = Math.max(h, metrics.getHeight());
		}
		if (hasTitle()) {
			h += ICON_TEXT_GAP;
		}
		/*String text = fullText.replace("<br>", "\n");
		//tempTextArea.setFont(tempTextArea.getFont().deriveFont(10f));
		//tempTextArea.setFont(textLabel.getFont());
		//System.out.println(textLabel.getFont());
		tempTextArea.setText(text);

		if ((text != null) && (text.length() > 0)) {
			FontMetrics metrics = textLabel.getFontMetrics(textLabel.getFont());
			int rowHeight = metrics.getHeight();
			int fullWidth = metrics.stringWidth(text + "__");
			if (fullWidth > w) {
				w = Math.max(w, Math.min(fullWidth, MAX_TEXT_WIDTH));
			}

			int lines = tempTextArea.getLineCount();
			try {
				for (int i = 0; i < tempTextArea.getLineCount(); i++) {
					int start = tempTextArea.getLineStartOffset(i);
					int end = tempTextArea.getLineEndOffset(i);
					String line = tempTextArea.getText(start, end - start);
					int width = metrics.stringWidth(line + "__");
					if (width > MAX_TEXT_WIDTH) {
						lines += (int) Math.ceil(width / (double) w) - 1;
					}
				}
			} catch (Exception ex) {
			}
			h += (lines * rowHeight) + ICON_TEXT_GAP;
		}*/

		w += textLabel.getPreferredSize().getWidth();
		h += textLabel.getPreferredSize().getHeight();

		Insets insets = getBorderInsets(balloonPosition);
		Dimension dim = new Dimension(w + insets.left + insets.right, h + insets.top + insets.bottom);
		setSize(dim);
		setPreferredSize(dim);
	}

	private void adjustTitleBorder() {
		if (hasTitle()) {
			this.titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, ICON_TEXT_GAP, 0));
		} else {
			this.titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
		}
	}

	private boolean hasTitle() {
		return (titleLabel.getIcon() != null) || (titleLabel.getText().length() > 0);
	}

	public Icon getTitleIcon() {
		return titleLabel.getIcon();
	}

	public void setTitleIcon(Icon icon) {
		this.titleLabel.setIcon(icon);
		adjustTitleBorder();
		adjustSize();
	}

	public String getText() {
		return textLabel.getText();
	}

	public void setText(String text) {
		textLabel.setText("<html>" + text + "</html>");
		adjustSize();
	}

	public String getTitle() {
		return titleLabel.getText();
	}

	public void setTitle(String title) {
		titleLabel.setText(title);
		adjustTitleBorder();
		adjustSize();
	}

	public Color getTextColor() {
		return textLabel.getForeground();
	}

	public void setTextColor(Color textColor) {
		if (textColor != null) {
			textLabel.setForeground(textColor);
		} else {
			textLabel.setForeground(Color.black);
		}
	}

	public Font getTextFont() {
		return textLabel.getFont();
	}

	public void setTextFont(Font textFont) {
		if (textFont != null) {
			textLabel.setFont(textFont);
		} else {
			textLabel.setFont(defaultFont);
		}
		adjustSize();
	}

	public Color getTitleColor() {
		return titleLabel.getForeground();
	}

	public void setTitleColor(Color titleColor) {
		if (titleColor != null) {
			titleLabel.setForeground(titleColor);
		} else {
			titleLabel.setForeground(Color.black);
		}
	}

	public Font getTitleFont() {
		return titleLabel.getFont();
	}

	public void setTitleFont(Font titleFont) {
		if (titleFont != null) {
			titleLabel.setFont(titleFont);
		} else {
			titleLabel.setFont(defaultFont.deriveFont(Font.BOLD, defaultFont.getSize2D() + 1f));
		}
		adjustSize();
	}

	public void setThickBorder(boolean thick) {
		if (thick != thickBorder) {
			this.thickBorder = thick;
			if (thick) {
				this.borderStroke = new BasicStroke(2f);
			} else {
				this.borderStroke = new BasicStroke(1f);
			}
		}
	}

	public boolean isThickBorder() {
		return thickBorder;
	}

	public Color getBorderColor() {
		return borderColor;
	}

	public void setBorderColor(Color borderColor) {
		if (borderColor != null) {
			this.borderColor = borderColor;
		} else {
			this.borderColor = Color.black;
		}
	}

	public Color getFillColor() {
		return fillColor;
	}

	public void setFillColor(Color fillColor) {
		if (fillColor != null) {
			this.fillColor = fillColor;
		} else {
			this.fillColor = Color.white;
		}
	}

	public static void main(String[] args) {
		JDialog dlg = new JDialog();
		dlg.setModal(true);
		dlg.setTitle("Balloon Test");

		JPanel panel = new JPanel(null);
		dlg.getContentPane().add(panel, BorderLayout.CENTER);
		BalloonPanel balloon = new BalloonPanel(
				"Balloon Title",
				Icons.getAddSlotIcon(),
				"A very long string that is very long and even longer than before and adding more text to make it more lines than before.\nA very long string that is very long and even longer than before and adding more text to make it more lines than before.",
				BELOW_RIGHT);
		//BalloonPanel balloon = new BalloonPanel("A short string no title");
		panel.add(balloon);
		balloon.setLocation(50, 50);
		//set up some sample properties of the balloon
		balloon.setTitleColor(new Color(0, 0, 128));
		balloon.setThickBorder(true);
		balloon.setBorderColor(Color.gray);
		balloon.setFillColor(new Color(255, 255, 220));

		dlg.pack();
		Dimension dim = new Dimension(500, 500);
		dlg.setSize(dim);
		dlg.setPreferredSize(dim);
		dlg.setLocation(400, 200);
		dlg.setVisible(true);
		System.exit(0);
	}

	private static Insets getBorderInsets(int position) {
		switch (position) {
		case ABOVE_LEFT:
		case ABOVE_RIGHT:
			return new Insets(PAD, PAD, PAD + SPIKE, PAD);
		case BELOW_LEFT:
		case BELOW_RIGHT:
			return new Insets(PAD + SPIKE, PAD, PAD, PAD);
		}
		return new Insets(PAD, PAD, PAD, PAD);
	}

	class BalloonBorder extends EmptyBorder {
		private static final long serialVersionUID = -9065148834035620855L;

		public BalloonBorder(Insets insets) {
			super(insets);
		}

		@Override
		public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
			Graphics2D g2 = (Graphics2D) g;
			g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			int borderAdjust = (thickBorder ? 1 : 0);
			Shape shape = createShape(x + borderAdjust, y, width - 1 - borderAdjust, height - 1);
			g2.setColor(getFillColor());
			g2.fill(shape);
			g2.setStroke(borderStroke);
			g2.setColor(getBorderColor());
			g2.draw(shape);
		}

		private Shape createShape(int x, int y, int w, int h) {
			GeneralPath path = new GeneralPath();
			switch (balloonPosition) {
			case ABOVE_LEFT:
				path.moveTo(x + PAD, y);
				path.lineTo(x + w - PAD, y);
				addRightCurve(path, x + w - PAD, y, PAD, h - SPIKE);
				path.lineTo(x + w - PAD, y + h);
				path.lineTo(x + w - (2 * PAD), y + h - SPIKE);
				path.lineTo(x + PAD, y + h - SPIKE);
				addLeftCurve(path, x, y, PAD, h - SPIKE);
				break;
			case ABOVE_RIGHT:
				path.moveTo(x + PAD, y);
				path.lineTo(x + w - PAD, y);
				addRightCurve(path, x + w - PAD, y, PAD, h - SPIKE);
				path.lineTo(x + (2 * PAD), y + h - SPIKE);
				path.lineTo(x + PAD, y + h);
				path.lineTo(x + PAD, y + h - SPIKE);
				addLeftCurve(path, x, y, PAD, h - SPIKE);
				break;
			case BELOW_LEFT:
				path.moveTo(x + PAD, y + SPIKE);
				path.lineTo(x + w - (2 * PAD), y + SPIKE);
				path.lineTo(x + w - PAD, y);
				path.lineTo(x + w - PAD, y + SPIKE);
				addRightCurve(path, x + w - PAD, y + SPIKE, PAD, h - SPIKE);
				path.lineTo(x + PAD, y + h);
				addLeftCurve(path, x, y + SPIKE, PAD, h - SPIKE);
				break;
			case BELOW_RIGHT:
				path.moveTo(x + PAD, y + SPIKE);
				path.lineTo(x + PAD, y);
				path.lineTo(x + (2 * PAD), y + SPIKE);
				path.lineTo(x + w - PAD, y + SPIKE);
				addRightCurve(path, x + w - PAD, y + SPIKE, PAD, h - SPIKE);
				path.lineTo(x + PAD, y + h);
				addLeftCurve(path, x, y + SPIKE, PAD, h - SPIKE);
				break;
			}
			return path;
		}

		// adds a left curve starting at the bottom going clockwise
		private void addLeftCurve(GeneralPath path, int x, int y, int arcWidth, int h) {
			path.quadTo(x, y + h, x, y + h - arcWidth);
			path.lineTo(x, y + arcWidth);
			path.quadTo(x, y, x + arcWidth, y);
		}

		// adds a right curve starting at the bottom going clockwise
		private void addRightCurve(GeneralPath path, int x, int y, int arcWidth, int h) {
			path.quadTo(x + arcWidth, y, x + arcWidth, y + arcWidth);
			path.lineTo(x + arcWidth, y + h - arcWidth);
			path.quadTo(x + arcWidth, y + h, x, y + h);
		}
	}

}
